use std::error::Error;

use axum::{
    http::{header, HeaderValue, StatusCode},
    response::{IntoResponse, Response},
};
use bytes::{BufMut, BytesMut};
use sea_orm::DbErr;
use serde::Serialize;
use validator::ValidationErrors;

pub type AppResult<T> = Result<T, AppError>;

#[derive(Debug, Clone, Serialize)]
struct ErrResult {
    code: u32,
    message: String,
}

impl ErrResult {
    fn new(code: u32, message: String) -> Self {
        Self { code, message }
    }

    fn status_code(&self) -> Result<StatusCode, Response> {
        StatusCode::from_u16((self.code / 100_00) as u16)
            .map_err(|err| (StatusCode::INTERNAL_SERVER_ERROR, err.to_string()).into_response())
    }
}

impl IntoResponse for ErrResult {
    fn into_response(self) -> Response {
        let mut buf = BytesMut::with_capacity(128).writer();
        match serde_json::to_writer(&mut buf, &self) {
            Ok(()) => (
                match self.status_code() {
                    Ok(code) => code,
                    Err(err) => return err,
                },
                [(
                    header::CONTENT_TYPE,
                    HeaderValue::from_static(mime::APPLICATION_JSON.as_ref()),
                )],
                buf.into_inner().freeze(),
            )
                .into_response(),
            Err(err) => (StatusCode::INTERNAL_SERVER_ERROR, err.to_string()).into_response(),
        }
    }
}

#[derive(Debug)]
pub enum AppError {
    DbErr(DbErr),
    JinjaError(tera::Error),
    NotFount,
    Forbidden,
    MissParam(String),
    BadParam(String),
    Custom(StatusCode, String),
    Response(axum::response::Response),
}

impl IntoResponse for AppError {
    fn into_response(self) -> axum::response::Response {
        match self {
            AppError::DbErr(e) => {
                ErrResult::new(500 * 100_00 + 11_00, format!("{}. {:?}", e, e.source()))
                    .into_response()
            }
            AppError::NotFount => (StatusCode::NOT_FOUND, "Not found").into_response(),
            AppError::MissParam(param) => {
                ErrResult::new(400 * 100_00 + 12_00, format!("Miss param: {}", param))
                    .into_response()
            }
            AppError::BadParam(param) => {
                ErrResult::new(500 * 100_00 + 13_00, format!("Bad param: {}", param))
                    .into_response()
            }
            AppError::JinjaError(e) => {
                ErrResult::new(500 * 100_00 + 14_00, format!("{}. {:?}", e, e.source()))
                    .into_response()
            }
            AppError::Forbidden => (axum::http::StatusCode::FORBIDDEN, "Forbidden").into_response(),
            AppError::Custom(status, message) => (status, message).into_response(),
            AppError::Response(res) => res,
        }
    }
}

impl From<DbErr> for AppError {
    fn from(value: DbErr) -> Self {
        Self::DbErr(value)
    }
}

impl From<ValidationErrors> for AppError {
    fn from(value: ValidationErrors) -> Self {
        let msg = value.errors().keys().fold(String::new(), |init, key| {
            let seq = if init.len() == 0 { "" } else { ", " };
            format!("{init}{seq}{key}")
        });
        Self::MissParam(msg)
    }
}

impl From<tera::Error> for AppError {
    fn from(value: tera::Error) -> Self {
        Self::JinjaError(value)
    }
}

impl From<Response> for AppError {
    fn from(value: Response) -> Self {
        AppError::Response(value)
    }
}

impl From<gen_table::error::Error> for AppError {
    fn from(value: gen_table::error::Error) -> Self {
        match value {
            gen_table::error::Error::DbErr(e) => e.into(),
            _ => todo!(),
        }
    }
}
